import { Actor, createActor, fromPromise } from 'xstate';
import { coreMachine } from 'sdk/fsm/weila_fsm';
import { ResultCodeMsg, WL_CoreEvent, 
         WL_CoreEventID, WL_LocationData, WL_LoginParam, 
         WL_MediaData, WL_MsgData, 
         WL_NetworkEvent, WL_NetworkEventID, WL_NetworkState, 
         WL_NotificationType, WL_PromiseCallback, WL_PttData, 
         WL_RecvNetData, WL_SendingMsgData, 
         WL_SessionInfo, WL_UserInfo, WeilaNetworkInterface, 
         WeilaStorageInterface} from 'sdk/store/weila_datas';
import { WL } from 'sdk/proto/weilapb';
import { WLBuildMsgRet } from 'sdk/proto/weilapb_wrapper_data';
import { WeilaStore } from 'sdk/store/weila_store'
import { WeilaPBLoginWrapper } from 'sdk/proto/weilapb_login_wrapper';
import { WeilaPbGroupWrapper } from 'sdk/proto/weilapb_group_wrapper'
import { WeilaPbFriendWrapper } from 'sdk/proto/weilapb_friend_wrapper';
import { WeilaPBSessionWrapper } from 'sdk/proto/weilapb_session_wrapper' 
import { WeilaPbBusinessWrapper } from 'sdk/proto/weilapb_business_wrapper';
import { TinyEmitter } from 'tiny-emitter';
import { convertFromMsgDataRaws, convertFromSessionRaw } from 'sdk/proto/weilapb_utils'
import Long from 'long';
import { WeilaMsgHeader } from 'sdk/proto/weilapb_msg_header';
import { get_pttdata_array_by_audiodata } from './weila_utils';


export class WeilaCore {
    coreActor: Actor<any>;
    isConnected: boolean;
    sendingMsgDataList: WL_SendingMsgData[];
    waitingRspMsgDataList: Map<number, WL_SendingMsgData>;
    supervisorTimer: any;
    heartbeatTimer: any;
    refreshTokenTimer: any;
    lastSentHeartbeatTime: number;
    connect_retry_times: number;
    login_retry_times: number;
    notificationEmitter: TinyEmitter

    // network
    network: WeilaNetworkInterface | null;
    readonly maxRecvBufSize = 1024*1024;
    private recvBuffer: Uint8Array;
    private currentRecv: number;
    private networkEmitter: TinyEmitter
    //

    constructor() {

        this.network = null;
        this.recvBuffer = new Uint8Array(this.maxRecvBufSize);
        this.currentRecv = 0;
        this.networkEmitter = new TinyEmitter();

        const m = coreMachine.provide({
            actions: {
                uninit_entry: this.on_uninit_entry.bind(this),
                uninit_exit: this.on_uninit_exit.bind(this),
                init_entry: this.on_init_entry.bind(this),
                init_exit: this.on_init_exit.bind(this),
                connect_entry: this.on_connect_entry.bind(this),
                connect_exit: this.on_connect_exit.bind(this),
                logining_entry: this.on_logining_entry.bind(this),
                logining_exit: this.on_logining_exit.bind(this),
                ready_entry: this.on_ready_entry.bind(this),
                ready_exit: this.on_ready_exit.bind(this),
                on_wait_net_disconnected_entry: this.on_wait_net_disconnected_entry.bind(this),
                on_wait_net_disconnected_exit: this.on_wait_net_disconnected_exit.bind(this),
                init: this.on_init.bind(this),
                on_start_login: this.on_start_login.bind(this),
                on_login_done: this.on_login_done.bind(this),
                on_login_error_and_try: this.on_login_error_and_try.bind(this),
                on_login_error: this.on_login_error.bind(this),
                on_connect_done: this.on_connect_done.bind(this),
                on_connect_error: this.on_connect_error.bind(this),
                on_connect_error_and_try: this.on_connect_error_and_try.bind(this),

                send_event: this.on_send_event.bind(this),
                on_server_kickout: this.on_server_kickout.bind(this),
                on_log_out: this.on_log_out.bind(this),
                onLogoutDone: this.onLogoutDone.bind(this),
                onLogoutFailed: this.onLogoutFailed.bind(this),
                on_reset_network: this.on_reset_network.bind(this),
                on_disconnect_done: this.on_disconnect_done.bind(this),
                on_disconnect_failed: this.on_disconnect_failed.bind(this),
                on_relogin: this.on_relogin.bind(this),
                on_network_disconnect: this.on_network_disconnect.bind(this),
            },
            guards: {
                should_try_login: this.should_try_login.bind(this),
                can_retry_connect: this.can_retry_connect.bind(this),      
            },
            actors: {
                login: fromPromise(this.login_server.bind(this)),
                connect: fromPromise(this.connect_server.bind(this)),
                logout_server: fromPromise(this.logout_server.bind(this)),
                disconnect_network: fromPromise(this.disconnect_network.bind(this)),
            }
        })

        this.coreActor = createActor(m);
        this.coreActor.subscribe((state) => {
            //TODO: log state
            console.log("current state: ", state);
        })
        this.coreActor.start();

        this.sendingMsgDataList = [];
        this.waitingRspMsgDataList = new Map();

        this.isConnected = false;
        this.lastSentHeartbeatTime = 0;
        this.login_retry_times = 0;
        this.connect_retry_times = 0;
        this.notificationEmitter = new TinyEmitter();
    }

    private network_on_connect() {
        console.log("network_on_connect");
        this.isConnected = true;
        const netEvent = {} as WL_NetworkEvent;
        netEvent.eventId = WL_NetworkEventID.NET_STATE_IND_EVT;
        netEvent.eventData = WL_NetworkState.NET_CONNECTED_STATE;
        this.networkEmitter.emit('message', netEvent);
    }

    private network_on_disconnect() {
        console.log("network_on_disconnect");
        this.isConnected = false;
        const netEvent = {} as WL_NetworkEvent;
        netEvent.eventId = WL_NetworkEventID.NET_STATE_IND_EVT;
        netEvent.eventData = WL_NetworkState.NET_DISCONNECTED_STATE;
        this.networkEmitter.emit('message', netEvent);        
    }

    private network_on_error(error: any) {
        console.log("network_on_error", error);
        this.isConnected = false;
        const netEvent = {} as WL_NetworkEvent;
        netEvent.eventId = WL_NetworkEventID.NET_EXCEPTION_IND_EVT;
        netEvent.eventData = JSON.stringify(error);
        this.networkEmitter.emit('message', netEvent);
    }

    private network_on_recv(recvData: Uint8Array) {
        if (this.currentRecv < this.maxRecvBufSize) {
            this.recvBuffer.set(recvData, this.currentRecv);
            this.currentRecv += recvData.length;
            
            while (this.currentRecv >= WeilaMsgHeader.msgHeaderLen) {
                const weilaHeader = WeilaMsgHeader.parseFromBuffer(this.recvBuffer);
                if (weilaHeader) {
                    const headerValue = weilaHeader.getValue();
                    if (this.currentRecv >= headerValue.length) {
                        const recvMsgData : WL_RecvNetData = {};
                        recvMsgData.header = headerValue;
                        recvMsgData.msgBinBuffer = this.recvBuffer.slice(WeilaMsgHeader.msgHeaderLen, headerValue.length);
                        this.recvBuffer.copyWithin(0, headerValue.length, this.currentRecv);
                        this.currentRecv -= headerValue.length;

                        const netEvent = {} as WL_NetworkEvent;
                        netEvent.eventId = WL_NetworkEventID.NET_MSG_RECV_IND_EVT;
                        netEvent.eventData = recvMsgData;
                        
                        this.networkEmitter.emit('message', netEvent);
                    }else {
                        break;
                    }
                }else {
                    console.log('!!!!!!!!!!!!!!!严重错误，收到错误的数据!!!!!!!!!!!!!!!!!!!!');
                    let buf = this.recvBuffer.reduce((previousValue, currentValue) => {return (previousValue + currentValue.toString(16).padStart(2, '0'))}, '');
                    console.log(buf);
                }
            }
        }        
    }

    // implement actions here, with params of (args: {context: any, event: any}, params: any)
    private on_uninit_entry(args: {context: any, event: any}, params: any) {
        console.log('uninit entry', args, params);
    }
    private on_uninit_exit(args: {context: any, event: any}, params: any) {
        console.log('uninit exit', args, params);
    }
    private on_init_entry(args: {context: any, event: any}, params: any) {        
        // TODO: initialize 
        this.login_retry_times = 0;
        this.connect_retry_times = 0;
        console.log('init entry', args, params);
    }
    private on_init_exit(args: {context: any, event: any}, params: any) {        
        console.log('init exit', args, params);
    }
    private on_logining_entry(args: {context: any, event: any}, params: any) {
        console.log('logining entry', args, params);
        this.networkEmitter.on('message', this.onNetworkMessage.bind(this));
    }
    private on_logining_exit(args: {context: any, event: any}, params: any) {
        this.networkEmitter.off('message');
        console.log('logining exit', args, params);
    }
    private on_connect_entry(args: {context: any, event: any}, params: any) {
        console.log('connect entry', args, params);
        this.isConnected = false
    }
    private on_connect_exit(args: {context: any, event: any}, params: any) {
        console.log('connect exit', args, params);
    }
    private on_ready_entry(args: {context: any, event: any}, params: any) {
        console.log('ready entry', args, params);
        this.networkEmitter.on('message', this.onNetworkMessage.bind(this));
        this.lastSentHeartbeatTime = Date.now();
        this.heartbeatTimer = setTimeout(this.heartbeatHandler.bind(this), 5000);
        this.refreshTokenTimer = setInterval(this.refreshToken.bind(this), 1000*60*60);
    }
    private on_ready_exit(args: {context: any, event: any}, params: any) {
        console.log('ready exit', args, params);
        clearTimeout(this.heartbeatTimer);
        clearInterval(this.refreshTokenTimer);
    }

    private on_wait_net_disconnected_entry(args: {context: any, event: any}, params: any) {
        console.log('on_wait_net_disconnected_entry', args, params);
        if (this.isConnected) {
            this.networkEmitter.on('message', (eventData: any) => {
                const netEvent = eventData as WL_NetworkEvent;
                console.log('net event', netEvent);
                switch (netEvent.eventId) {
                    case WL_NetworkEventID.NET_EXCEPTION_IND_EVT:
                    case WL_NetworkEventID.NET_STATE_IND_EVT: {
                        if (netEvent.eventId == WL_NetworkEventID.NET_STATE_IND_EVT) {
                            let state = netEvent.eventData as WL_NetworkState;
                            if (state != WL_NetworkState.NET_DISCONNECTED_STATE && state != WL_NetworkState.NET_CONNECTING_STATE) {
                                break;
                            }
                        }
                            
                        setTimeout(() => {
                            console.log('wait 1 sec to reconnect');
                            let loginParam = WeilaStore.getInstance().getLoginParam();
                            if (loginParam && loginParam.promiseCallback) {
                                loginParam.promiseCallback = undefined;
                            }
                            this.coreActor.send({ type: 'relogin', loginParam} as any);
                        }, 1000);

                        this.networkEmitter.off('message');
                    }
                    break;
                }
                
            })
        }else {
            setTimeout(() => {
                console.log('wait 1 sec to reconnect');
                
                let loginParam = WeilaStore.getInstance().getLoginParam();
                if (loginParam && loginParam.promiseCallback) {
                    loginParam.promiseCallback = undefined;
                }
                this.coreActor.send({ type: 'relogin', loginParam} as any);
            }, 1000);            
        }
    }

    private on_wait_net_disconnected_exit(args: {context: any, event: any}, params: any) {
        console.log('on_wait_net_disconnected_exit', args, params);
    }

    private on_reset_network(args: {context: any, event: any}, params: any) {
        console.log('on_reset_network', args, params);
    }

    private on_disconnect_done(args: {context: any, event: any}, params: any) {
        console.log('on_disconnect_done', args, params);
    }

    private on_disconnect_failed(args: {context: any, event: any}, params: any) {
        console.log('on_disconnect_failed', args, params);
    }

    private on_relogin(args: {context: any, event: any}, params: any) {
        console.log('on_relogin', args, params);
    }

    private on_network_disconnect(args: {context: any, event: any}, params: any) {
        console.log('on_network_disconnect', args, params);
    }

    private onLogoutFailed(args: {context: any, event: any}, params: any) {
        console.log('onLogoutFailed', args, params);
        if (args.event.error.param && args.event.error.param.promiseCallback) {
            args.event.error.param.promiseCallback.reject(args.event.error.error);
        }
    }

    private onLogoutDone(args: {context: any, event: any}, params: any) {
        console.log('onLogoutDone', args, params);
        if (args.event.output && args.event.output.promiseCallback) {
            console.log('output', args.event.output);
            args.event.output.promiseCallback.resolve("success");
        }
    }

    private on_init(args: {context: any, event: any}, params: any) {
        console.log('init', args, params);
    }

    private on_start_login(args: {context: any, event: any}, params: any) {
        console.log('start login', args, params);
    }

    private on_login_error_and_try(args: {context: any, event: any}, params: any) {
        console.log('login error and try', args, params);
    }

    private on_login_done(args: {context: any, event: any}, params: any) {
        console.log('login done', args, params);
        const loginParam = args.event.output.param as WL_LoginParam;
        if (loginParam) {
            if (loginParam.promiseCallback) {
                loginParam.promiseCallback.resolve(args.event.output.result);
            }
        }

        loginParam.promiseCallback = undefined;
        this.login_retry_times = 0;
        WeilaStore.getInstance().setLoginParam(loginParam);
        
        const loginRsp = args.event.output.result as WL.Login.IRspLoginApp;
        if (loginRsp) {
            WeilaStore.getInstance().setLoginToken(loginRsp.token!);
            WeilaStore.getInstance().setLoginUserInfo(loginRsp.userInfo!);
        }
    }

    private on_login_error(args: {context: any, event: any}, params: any) {
        console.log('login error', args, params);
        const loginParam = args.event.error.param as WL_LoginParam;
        if (loginParam) {
            if (loginParam.promiseCallback) {
                loginParam.promiseCallback.reject(args.event.error.error);
            }
        }
        WeilaStore.getInstance().setLoginParam(null);
    }

    private on_connect_done(args: {context: any, event: any}, params: any) {
        console.log('connect done', args, params);
        this.connect_retry_times = 0;
        this.isConnected = true;
    }

    private on_connect_error_and_try(args: {context: any, event: any}, params: any) {
        console.log('connect error and try', args, params);
    }

    private on_connect_error(args: {context: any, event: any}, params: any) {
        console.log('connect error', args, params);
        const loginParam = args.event.error.param as WL_LoginParam;
        if (loginParam) {
            if (loginParam.promiseCallback) {
                loginParam.promiseCallback.reject(args.event.error.error);
            }
        }
    }

    private on_send_event(args: {context: any, event: any}, params: any) {
        console.log('send event', args, params);
        const coreEvent = args.event.coreEvent as WL_CoreEvent;
        switch (coreEvent.eventId) {
            case WL_CoreEventID.CORE_EVT_SEND_SERVICE_MESSAGE: {
                if (this.sendingMsgDataList.length > 0) {
                    let sendingMsgData = this.sendingMsgDataList.shift()!;
                    this.waitingRspMsgDataList.set(sendingMsgData.buildPbMsg.weilaMsgHeader!.seqNum, sendingMsgData);
                    console.log('sendingMsgData seq', sendingMsgData.buildPbMsg.weilaMsgHeader!.seqNum);
                    
                    let netEvent: WL_NetworkEvent = {
                        eventId: WL_NetworkEventID.NET_SEND_DATA_EVT,
                        eventData: sendingMsgData.buildPbMsg.reqData
                    };

                    this.network?.send(sendingMsgData.buildPbMsg.reqData!);
                }

                if (this.sendingMsgDataList.length > 0) {
                    let nextEvent: WL_CoreEvent = {} as WL_CoreEvent;
                    nextEvent.eventId = WL_CoreEventID.CORE_EVT_SEND_SERVICE_MESSAGE;
                    nextEvent.eventData = null;
                    this.coreActor.send({ type: 'send_event', coreEvent: nextEvent} as any);
                }
            }
            break;
        
            default:
                break;
        }
    }

    private on_server_kickout(args: {context: any, event: any}, params: any) {
        console.log('server kickout', args, params);
    }

    private on_log_out(args: {context: any, event: any}, params: any) {
        console.log('log out', args, params);
    }

    // implement actors here
    private async login_server(parameters: any) {
        console.log('login server-------->', parameters);
        let loginParam: WL_LoginParam = parameters.input.loginParam;
        
        let buildMsgRet = WeilaPBLoginWrapper.buildLoginAppReq(
            loginParam.account, loginParam.password, loginParam.countryCode, 
            WeilaStore.getInstance().getAppId()!,
            WeilaStore.getInstance().getAppKey()!,
            loginParam.clientType,
            loginParam.isPasswordMd5);

        console.log('loginParam', loginParam);
        console.log('client', loginParam.clientType);
        console.log('appid', WeilaStore.getInstance().getAppId());
        console.log('appkey', WeilaStore.getInstance().getAppKey());
        console.log('buildMsgRet', buildMsgRet.resultCode);

        if (buildMsgRet.resultCode != 0) {
            return Promise.reject({param: loginParam, error: new Error(ResultCodeMsg.get(buildMsgRet.resultCode))});
        }

        let result;
        try {
            result = await this.sendServiceMessage(buildMsgRet, 20000);
            console.log('sendServiceMessage', result);
        }catch (e) {
            return Promise.reject({param: loginParam, error: e});
        }

        return {param: loginParam, result};
    }

    private async connect_server(parameters: any) {
        console.log('connect server', parameters);
        
        return new Promise<any>((resolve, reject) => {
            const connectNetworkTimer = setTimeout(() => {
                reject({param: parameters.input.loginParam, error: new Error('connect network timeout')});
            }, 10000)
            this.networkEmitter.on('message', (event: any) => {
                const netEvent = event as WL_NetworkEvent;
                console.log('net event', netEvent);
                switch (netEvent.eventId) {
                    case WL_NetworkEventID.NET_STATE_IND_EVT: {
                        let state = netEvent.eventData as WL_NetworkState;
                        if (state == WL_NetworkState.NET_CONNECTED_STATE) {
                            this.isConnected = true;
                            this.networkEmitter.off('message');
                            // 把登录参数传给登录成功的回调
                            clearTimeout(connectNetworkTimer);
                            resolve(parameters.input.loginParam);

                        }
                    }
                    break;

                    case WL_NetworkEventID.NET_EXCEPTION_IND_EVT: {
                        // 出错后，也要把登录参数传给登录失败的回调
                        this.networkEmitter.off('message');
                        clearTimeout(connectNetworkTimer);
                        reject({param: parameters.input.loginParam, error: netEvent.eventData});
                    }
                    break
                }
                
            })
            
            this.network?.connect(WeilaStore.getInstance().getWebsocketAddr()!);
        })
    }

    private async logout_server(parameters: any): Promise<any> {
        console.log('logout server', parameters);
        return new Promise<any>(async (resolve, reject) => {
            const buildMsgRet = WeilaPBLoginWrapper.buildLogoutReq();
            try {
                const result = await this.sendServiceMessage(buildMsgRet, 2000);
                console.log('logout_server rsp', result);
            }catch (e) {
                console.log('logout error', e);
                this.network?.disconnect();
            }

            const disconnectNetTimer = setTimeout(() => {
                reject({param: parameters.input.logoutParam, error: new Error("disconnect network timeout")});
            }, 10000);

            this.networkEmitter.on('message', (event: any) => {
                const netEvent = event as WL_NetworkEvent;
                console.log('logout_server net event', netEvent);
                switch (netEvent.eventId) {
                    case WL_NetworkEventID.NET_STATE_IND_EVT: {
                        let state = netEvent.eventData as WL_NetworkState;
                        console.log('NET_STATE_IND_EVT state', state);
                        
                        if (state == WL_NetworkState.NET_DISCONNECTED_STATE) {
                            this.isConnected = false;
                            // 把登出参数传给登录成功的回调，登出要等待网络自动断开
                            resolve(parameters.input.logoutParam);
                        }

                        clearTimeout(disconnectNetTimer);
                        this.networkEmitter.off('message');
                    }
                    break;

                    case WL_NetworkEventID.NET_EXCEPTION_IND_EVT: {
                        console.log('NET_EXCEPTION_IND_EVT', netEvent.eventData);
                        this.isConnected = false;
                        reject({param: parameters.input.logoutParam, error: netEvent.eventData});
                        clearTimeout(disconnectNetTimer);
                        this.networkEmitter.off('message');
                    }
                    break
                }
            })
        })
    }

    private async disconnect_network(parameters: any) {
        return new Promise<any>((resolve, reject) => {
            this.networkEmitter.on('message', (event: any) => {
                const netEvent = event as WL_NetworkEvent;
                console.log('net event', netEvent);
                switch (netEvent.eventId) {
                    case WL_NetworkEventID.NET_DISCONNECT_EVT: {
                        this.isConnected = false;
                        resolve(true);
                        this.networkEmitter.off('message');
                    }
                    break;

                    case WL_NetworkEventID.NET_EXCEPTION_IND_EVT: {
                        this.isConnected = false;
                        reject(netEvent.eventData);
                        this.networkEmitter.off('message');
                    }
                    break
                }
                
            })
            this.networkEmitter.emit('message', { eventId: WL_NetworkEventID.NET_DISCONNECT_EVT } as WL_NetworkEvent);
        })
    }

    // implement guards here

    private should_try_login(args: {context: any, event: any}, params: any): boolean {    
        console.log('should_try_login', args, params);
        
        const loginParam = args.event.error.param as WL_LoginParam;
        if (loginParam && loginParam.promiseCallback) {
            return false;
        }

        if (this.login_retry_times < 5) {
            this.login_retry_times++;
            return true;
        }
        return false;
    }

    private can_retry_connect(args: {context: any, event: any}, params: any): boolean {
        console.log('can_retry_connect', args, params);
        const loginParam = args.event.error.param as WL_LoginParam;
        if (loginParam && loginParam.promiseCallback) {
            return false;
        }

        if (this.connect_retry_times < 5) {
            this.connect_retry_times++;
            return true;
        }
        return false;
    }

    private async refreshToken() {
        try {
            const buildRet = WeilaPBLoginWrapper.buildRefreshTokenReq();
            const ret = await this.sendServiceMessage(buildRet, 5000);
            WeilaStore.getInstance().setLoginToken(ret.accessToken!);
        }catch(e) {
            console.log('refresh token error', e);
        }
    }

    private heartbeatHandler() {
        console.log('heartbeat', Date.now());
        if (this.waitingRspMsgDataList.size != 0) {
            let keys = this.waitingRspMsgDataList.keys();
            let removeKeys = [];
            for (let key of keys) {
                let sendingInfo: WL_SendingMsgData = this.waitingRspMsgDataList.get(key)!;
                if (sendingInfo.timeout < Date.now()) {
                    if (sendingInfo.promiseCallback) {
                        sendingInfo.promiseCallback.reject(ResultCodeMsg.get(WL.Service.ResultCode.RESULT_REQUEST_TIMEOUT));
                    }

                    removeKeys.push(key);
                }
            }

            for (let key of removeKeys) {
                this.waitingRspMsgDataList.delete(key);
            }
        }

        if (this.waitingRspMsgDataList.size == 0) {
            this.resetHeartbeat(5000);
        }

        // sending heartbeat every 10s
        console.log('different time', Date.now() - this.lastSentHeartbeatTime);
        
        if (Date.now() - this.lastSentHeartbeatTime > 10000) {
            this.sendHeartbeat();
            this.lastSentHeartbeatTime = Date.now();
        }
    }

    private async sendHeartbeat() {
        try {
            const buildRet = WeilaPBLoginWrapper.buildHeartbeatReq();
            const ret = await this.sendServiceMessage(buildRet, 5000);
            console.log('sendHeartbeat', ret);
        }catch(e) {
            console.log('发送心跳失败', e);
        }
    }

    private resetHeartbeat(timeout: number) {
        clearTimeout(this.heartbeatTimer)
        if (this.coreActor.getSnapshot().value == 'ready') {
            this.heartbeatTimer = setTimeout(this.heartbeatHandler.bind(this), timeout)
        }
    }

    private onNetworkMessage(eventData: any) {
        console.log('net event', eventData);
        let netEvent: WL_NetworkEvent = eventData as WL_NetworkEvent;
        switch (netEvent.eventId) {
            case WL_NetworkEventID.NET_STATE_IND_EVT: {
                let state = netEvent.eventData as WL_NetworkState;
                if (state == WL_NetworkState.NET_DISCONNECTED_STATE) {
                    console.log('网络断开了');
                    this.isConnected = false;
                    this.coreActor.send({ type: 'network_disconnect' });
                }
            }
            break;

            case WL_NetworkEventID.NET_EXCEPTION_IND_EVT: {
                console.log('网络异常了');
                this.isConnected = false;
                this.coreActor.send({ type: 'network_disconnect' });
            }break;

            case WL_NetworkEventID.NET_MSG_RECV_IND_EVT: {
                let recvData: WL_RecvNetData = netEvent.eventData as WL_RecvNetData;
                console.log('recvData', recvData);
                this.onRecvMsgData(recvData);
            }
            break;
        }
    }

    protected sendServiceMessage(rawMsg: WLBuildMsgRet, timeout: number = 5000) {
        /**
         * 等待消息，带上超时，带上Promise的回调
         */
        return new Promise<any>((resolve, reject) => {
            let sendingMsgData: WL_SendingMsgData = {
                buildPbMsg: rawMsg,
                timeout: Date.now() + timeout,
                promiseCallback: {
                    resolve: resolve,
                    reject: reject
                }
            };

            this.sendingMsgDataList.push(sendingMsgData);
            let coreEvent: WL_CoreEvent = {
                eventId: WL_CoreEventID.CORE_EVT_SEND_SERVICE_MESSAGE,
                eventData: sendingMsgData
            }
            this.coreActor.send({ type: 'send_event', coreEvent} as any);
        })
    }

    protected onMsgResponse(serverMsg: WL.Service.IServiceMessage) {
        console.log('onMsgResponse', serverMsg);
        let sendingItem: WL_SendingMsgData|undefined = this.waitingRspMsgDataList.get(serverMsg.serviceHead!.seq!)
        if (sendingItem) {
            if (serverMsg.serviceHead!.resultCode != WL.Service.ResultCode.RESULT_SUCCESS) {
                sendingItem.promiseCallback?.reject(ResultCodeMsg.get(serverMsg.serviceHead!.resultCode!));
            }else {
                switch (serverMsg.serviceHead!.serviceId!) {
                    case WL.Service.ServiceID.SERVICE_LOGIN: {
                        switch (serverMsg.serviceHead!.commandId!) {
                            case WL.Login.LoginCommandId.LOGIN_COMMAND_LOGIN_APP: {
                                sendingItem.promiseCallback?.resolve(serverMsg.loginMessage?.rspLoginApp);
                            }
                            break;

                            case WL.Login.LoginCommandId.LOGIN_COMMAND_BIND_EXTENSION: {
                                sendingItem.promiseCallback?.resolve(serverMsg.loginMessage?.rspBindExtension);
                            }
                            break;

                            case WL.Login.LoginCommandId.LOGIN_COMMAND_UNBIND_EXTENSION: {
                                sendingItem.promiseCallback?.resolve(serverMsg.loginMessage?.rspUnbindExtension);
                            }
                            break;

                            case WL.Login.LoginCommandId.LOGIN_COMMAND_REFRESH_ACCESS_TOKEN: {
                                sendingItem.promiseCallback?.resolve(serverMsg.loginMessage?.rspRefreshAccessToken);
                            }
                            break;

                            default: {
                                console.log('onMsgResponse command id and seq', serverMsg.serviceHead!.commandId!, serverMsg.serviceHead!.seq!);
                                sendingItem.promiseCallback?.resolve("Success");
                            }
                            break;
                        }
                    }
                    break;

                    case WL.Service.ServiceID.SERVICE_SESSION: {
                        switch (serverMsg.serviceHead!.commandId!) {
                            case WL.Session.SessionCommandId.SESSION_COMMAND_GET_SESSION: {
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage?.rspGetSession);
                            }
                            break;

                            case WL.Session.SessionCommandId.SESSION_COMMAND_GET_MSG: {
                                const userIfo: WL_UserInfo|null = WeilaStore.getInstance().getLoginUserInfo();
                                const msgDatas : WL_MsgData[] = convertFromMsgDataRaws(serverMsg.sessionMessage!.rspGetMsg!.msgList!, userIfo!.userId!);
                                console.log('rsp SESSION_COMMAND_GET_MSG', serverMsg.sessionMessage!.rspGetMsg!.msgList, msgDatas);
                                
                                serverMsg.sessionMessage!.rspGetMsg!.msgList = msgDatas;
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage!.rspGetMsg);
                            }
                            break;

                            case WL.Session.SessionCommandId.SESSION_COMMAND_MSGDATA: {
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage?.rspMsg);                            }
                            break;

                            case WL.Session.SessionCommandId.SESSION_COMMAND_GET_ONLINE_MEMBER: {
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage?.rspGetSessionOnlineMember);
                            }
                            break;

                            case WL.Session.SessionCommandId.SESSION_COMMAND_GET_USERINFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage?.rspGetSessionUserInfo);
                            }
                            break;

                            case WL.Session.SessionCommandId.SESSION_COMMAND_GET_UNREAD_MSG: {
                                const userIfo: WL_UserInfo|null = WeilaStore.getInstance().getLoginUserInfo();
                                const msgDatas : WL_MsgData[] = convertFromMsgDataRaws(serverMsg.sessionMessage!.rspGetUnreadMsg!.msgList!, userIfo!.userId!);
                                serverMsg.sessionMessage!.rspGetUnreadMsg!.msgList! = msgDatas;
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage!.rspGetMsg);                                
                            }
                            break;

                            case WL.Session.SessionCommandId.SESSION_COMMAND_READ_MSG: {
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage?.rspMsgRead);
                            }
                            break;

                            case WL.Session.SessionCommandId.SESSION_COMMAND_BURST_CONTROL: {
                                sendingItem.promiseCallback?.resolve(serverMsg.sessionMessage?.rspBurstControl);
                            }
                            break;
                        }
                    }
                    break

                    case WL.Service.ServiceID.SERVICE_GROUP: {
                        switch (serverMsg.serviceHead!.commandId!) {
                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_GROUPVERSION: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupVersion);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_GROUPINFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupInfo);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_GROUPATTRIBUTE: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupAttribute);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_MEMBER_USERINFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetMemberUserInfo);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_GROUP_INVITEINFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupInviteInfo);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_GROUP_JOININFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupJoinInfo);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_ONLINE_MEMBER: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupOnlineMember);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_GROUPDETAIL: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupDetail);
                            }
                            break;

                            case WL.Group.GroupCommandId.GROUP_COMMAND_GET_BLACKLIST: {
                                sendingItem.promiseCallback?.resolve(serverMsg.groupMessage?.rspGetGroupBlackList);
                            }
                            break;
                        }
                    }
                    break;

                    case WL.Service.ServiceID.SERVICE_FRIEND: {
                        switch (serverMsg.serviceHead!.commandId!) {
                            case WL.Friend.FriendCommandId.FRIEND_COMMAND_GET_ONLINE_FRIEND: {
                                sendingItem.promiseCallback?.resolve(serverMsg.friendMessage?.rspGetOnlineFriend);
                            }
                            break;

                            case WL.Friend.FriendCommandId.FRIEND_COMMAND_GET_BLACKLIST: {
                                sendingItem.promiseCallback?.resolve(serverMsg.friendMessage?.rspGetFriendBlackList);
                            }
                            break;

                            case WL.Friend.FriendCommandId.FRIEND_COMMAND_GET_FRIENDINFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.friendMessage?.rspGetFriendInfo);
                            }
                            break;

                            case WL.Friend.FriendCommandId.FRIEND_COMMAND_GET_FRIEND_USERINFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.friendMessage?.rspGetFriendUserInfo);
                            }
                            break;

                            case WL.Friend.FriendCommandId.FRIEND_COMMAND_GET_FRIEND_INVITEINFO: {
                                sendingItem.promiseCallback?.resolve(serverMsg.friendMessage?.rspGetFriendInviteInfo);
                            }
                            break;
                        }
                    }
                    break;

                    case WL.Service.ServiceID.SERVICE_BUSINESS: {
                        switch (serverMsg.serviceHead!.commandId!) {
                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_GET_SESSION: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCommonGetSessions);
                            }
                            break;

                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_STAFF_INVITE: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCommonStaffInvite);
                            }
                            break;

                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_ANSWER_STAFF_INVITE: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCommonAnswerStaffInvite);
                            }
                            break;

                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_GET_STAFF_INVITE: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCommonGetStaffInvite);
                            }
                            break;

                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_STAFF_EXIT: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCommonStaffExit);
                            }
                            break;

                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_STAFF_DELETE: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCommonStaffDelete);
                            }
                            break;

                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_CUSTOMER_CLOSE_SESSION: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCustomerCloseSession);
                            }
                            break;

                            case WL.Business.BusinessCommandId.BUSINESS_COMMAND_CUSTOMER_ACTIVE_SESSION: {
                                sendingItem.promiseCallback?.resolve(serverMsg.businessMessage?.rspCustomerActiveSession);
                            }   
                            break;
                        }
                    }
                    break;
                }
            }
            this.waitingRspMsgDataList.delete(serverMsg.serviceHead!.seq!);
        }

        if (this.waitingRspMsgDataList.size == 0) {
            this.resetHeartbeat(2000);
        }
    }

    protected onMsgIndication(serverMsg: WL.Service.IServiceMessage) {
        console.log('onMsgIndication', serverMsg);
        switch (serverMsg.serviceHead!.serviceId!) {
            case WL.Service.ServiceID.SERVICE_SESSION: {
                switch (serverMsg.serviceHead!.commandId!) {
                    case WL.Session.SessionCommandId.SESSION_COMMAND_MSGDATA: {
                        const userIfo: WL_UserInfo|null = WeilaStore.getInstance().getLoginUserInfo();
                        const msgDatas : WL_MsgData[] = convertFromMsgDataRaws([serverMsg.sessionMessage!.ntfMsg!.msgData!], userIfo!.userId!);
                        console.log('sending notification to app', msgDatas[0]);
                        this.notificationEmitter.emit('notification', WL_NotificationType.SESSION_MSG_NOTIFICATION, msgDatas[0]);
                    }
                    break;
                }
            }
            break;

            case WL.Service.ServiceID.SERVICE_FRIEND: {
                switch (serverMsg.serviceHead!.commandId!) {
                    case WL.Friend.FriendCommandId.FRIEND_COMMAND_FRIEND_INVITE: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.FRIEND_INVITE_NOTIFICATION, serverMsg.friendMessage!.ntfFriendInvite);
                    }
                    break;

                    case WL.Friend.FriendCommandId.FRIEND_COMMAND_ANSWER_FRIEND_INVITE: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.FRIEND_ANSWER_NOTIFICATION, serverMsg.friendMessage!.ntfAnswerFriendInvite);
                    }
                    break;
                }
            }
            break;

            case WL.Service.ServiceID.SERVICE_GROUP: {
                switch (serverMsg.serviceHead!.commandId!) {
                    case WL.Group.GroupCommandId.GROUP_COMMAND_GROUP_INVITE: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.GROUP_INVITE_NOTIFICATION, serverMsg.groupMessage!.ntfGroupInvite);
                    }
                    break;

                    case WL.Group.GroupCommandId.GROUP_COMMAND_ANSWER_GROUP_INVITE: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.GROUP_ANSWER_INVITE_NOTIFICATION, serverMsg.groupMessage!.ntfAnswerGroupInvite);
                    }
                    break;

                    case WL.Group.GroupCommandId.GROUP_COMMAND_GROUP_JOIN: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.GROUP_JOIN_NOTIFICATION, serverMsg.groupMessage!.ntfGroupJoin);
                    }
                    break;

                    case WL.Group.GroupCommandId.GROUP_COMMAND_ANSWER_GROUP_JOIN: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.GROUP_ANSWER_JOIN_NOTIFICATION, serverMsg.groupMessage!.ntfAnswerGroupJoin);
                    }
                    break;
                }
            }
            break;

            case WL.Service.ServiceID.SERVICE_BUSINESS: {
                switch (serverMsg.serviceHead!.commandId!) {
                    case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_SESSION_CHANGE: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.BUSINESS_SESSION_CHANGED_NOTIFICATION, serverMsg.businessMessage!.ntfCommonSessionChange);
                    }
                    break;

                    case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_STAFF_INVITE: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.BUSINESS_STAFF_INVITE_NOTIFICATION, serverMsg.businessMessage!.ntfCommonStaffInvite);
                    }
                    break;

                    case WL.Business.BusinessCommandId.BUSINESS_COMMAND_COMMON_ANSWER_STAFF_INVITE: {
                        this.notificationEmitter.emit('notification', WL_NotificationType.BUSINESS_ANSWER_STAFF_INVITE_NOTIFICATION, serverMsg.businessMessage!.ntfCommonAnswerStaffInvite);
                    }
                    break;  
                }
            }
            break;
        }
    }

    protected onRecvMsgData(recvNetdata: WL_RecvNetData) {
        console.log('recvNetdata', recvNetdata);
        const serverMessage = WL.Service.ServiceMessage.decode(recvNetdata.msgBinBuffer!);
        if (serverMessage.serviceHead!.commandType == WL.Service.CommandType.COMMAND_RESPONSE) {
            this.onMsgResponse(serverMessage);
        }else if (serverMessage.serviceHead!.commandType == WL.Service.CommandType.COMMAND_NOTIFY) {
            this.onMsgIndication(serverMessage);
        }
    }
    

    /**
     * Weila core initialization. the two parameters must be provided.
     * @param network The network interface which will be used for communication with server.
     * @param storage The storage interface which will be used for storing data.
     */
    public weila_init(network: WeilaNetworkInterface, storage: WeilaStorageInterface) {
        if (!network || !storage) {
            throw new Error('network or storage is null');
        }

        WeilaStore.getInstance().setStorageImpl(storage);
        this.coreActor.send({ type: 'start_init' });
        this.network = network;
        this.network.onConnect(this.network_on_connect.bind(this));
        this.network.onClose(this.network_on_disconnect.bind(this));
        this.network.onRecv(this.network_on_recv.bind(this));
        this.network.onError(this.network_on_error.bind(this));        
    }


    /**
     * Login to Weila server.
     * 
     * @param account The user account of Weila.
     * @param password The password of the user account.
     * @param countryCode The country code of the user account.
     * @param clientType The client type which is used for login. Default is CLIENT_MINIPROGRAM, refer to WL.Common.ClientType
     * @param isPasswordMd5 Is the password already been MD5 encrypted. Default is false.
     * 
     * @returns a promise that resolved with a RspLoginApp when login done.
     */
    public async weila_login(account: string, password: string, countryCode: string, clientType: number = WL.Common.ClientType.CLIENT_MINIPROGRAM, isPasswordMd5?: boolean): Promise<WL.Login.RspLoginApp> {
        return new Promise((resolve, reject) => {
            let loginParam: WL_LoginParam = {
                retryCount: 0,
                account,
                password,
                countryCode,
                promiseCallback: {
                 resolve,
                 reject   
                },
                clientType: clientType,
                isPasswordMd5: isPasswordMd5 ? isPasswordMd5 : false   
            }

            this.coreActor.send({ type: 'start_login', loginParam } as any);
        })
    }

    /**
     * weila_logout
     * 
     * Logout from Weila server.
     * 
     * @returns a promise that resolved when logout done
     */
    public async weila_logout(): Promise<string> {
        console.log('weila_logout');
        
        return new Promise((resolve, reject) => {
            let logoutParam: WL_PromiseCallback = {
                resolve,
                reject
            }

            this.coreActor.send({ type: 'log_out', logoutParam } as any);
        })
    }

    /**
     * 来自服务器的消息，会以事件通知的方式传递给listener
     * 
     * @param listener listener that called when a notification received.
     * The listener should be a function that take two parameters, event and eventData.
     * The event parameter is a number that represent the type of the notification.
     * The eventData parameter is a json object that contains the data of the notification.
     */
    public weila_listenNotification(listener: (event: number, eventData: any) => void) {
        this.notificationEmitter.on('notification', listener);
    }


    // login service related functions

    /**
     * Refresh token.
     * 
     * When the token is about to expire, Weila will send a notification to the application.
     * The application should call this function to refresh the token.
     * If the token is expired, the application should call weila_login to login again.
     * 
     * @returns a promise that resolved when the token is refreshed
     */
    public async weila_refresh_token(): Promise<string> {
        console.log('weila_refresh_token');
        const buildRet = WeilaPBLoginWrapper.buildRefreshTokenReq();
        return this.sendServiceMessage(buildRet, 5000);
    }

    /**
     * Bind a device to the current user.
     * 
     * @param verifyCode the verification code sent to the device.
     * @returns a promise that resolved when the device is bound.
     */
    public async weila_bind_device(verifyCode: string): Promise<WL.Login.RspBindExtension> {
        const buildRet = WeilaPBLoginWrapper.buildBindExtensionReq(verifyCode);
        return this.sendServiceMessage(buildRet, 5000);
    }

    /**
     * Unbind a device from the current user.
     * 
     * @param deviceId the id of the device to unbind.
     * @returns a promise that resolved when the device is unbound.
     */
    public async weila_unbind_device(deviceId: number): Promise<WL.Login.RspUnbindExtension> {
        const buildRet = WeilaPBLoginWrapper.buildUnbindExtensionReq(deviceId);
        return this.sendServiceMessage(buildRet, 5000);
    }


    // group service related functions
    
    /**
     * Get the current version of the group service.
     * 
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the current version of the group service.
     */
    public async weila_get_group_version(timeout: number = 5000): Promise<WL.Group.RspGetGroupVersion> {
        const buildRet = WeilaPbGroupWrapper.buildGetGroupVersionReq();
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the information of a group.
     * 
     * @param groupId the id of the group to get.
     * @param memberVersion the version of the group members.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the information of the group.
     */
    public async weila_get_group_info(groupId: string, memberVersion: number, timeout: number = 5000): Promise<WL.Group.RspGetGroupInfo> {
        const groupIdLong = Long.fromString(groupId);
        const buildRet = WeilaPbGroupWrapper.buildGetGroupInfoReq(groupIdLong, memberVersion);
        return this.sendServiceMessage(buildRet, timeout);
    }

/**
 * Retrieves information about group members.
 *
 * @param groupId - The ID of the group as a string.
 * @param userIdList - An array of user IDs to get information for.
 * @param timeout - The maximum time to wait for the request in milliseconds. Default is 5000 ms.
 * @returns A promise that resolves to the response containing member user information.
 */

    public async weila_get_group_members_info(groupId: string, userIdList: number[], timeout: number = 5000 ): Promise<WL.Group.RspGetMemberUserInfo> {
        const groupIdLong = Long.fromString(groupId);
        const buildRet = WeilaPbGroupWrapper.buildGetMemberUserInfoReq(groupIdLong, userIdList);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the attributes of a group.
     * 
     * @param groupType the type of the group.
     * @param keyword the keyword to search for.
     * @param classType the class type of the group.
     * @param groupId the id of the group.
     * @param pageIndex the index of the page to get.
     * @param pageSize the size of the page to get.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the attributes of the group.
     */
    public async weila_get_group_attributes(groupType: number, keyword: string, 
        classType: number, groupId: string, 
        pageIndex: number = 0, pageSize: number = 100, timeout: number = 5000): Promise<WL.Group.RspGetGroupAttribute> {
        const groupIdLong = Long.fromString(groupId);
        const buildRet = WeilaPbGroupWrapper.buildGetGroupAttributeReq(groupType, keyword, classType, groupIdLong, pageIndex, pageSize);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the online members of a group.
     * 
     * @param groupId the id of the group.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the online members of the group.
     */
    public async weila_get_group_online_members(groupId: string, timeout: number = 5000): Promise<WL.Group.RspGetGroupOnlineMember> {
        const groupIdLong = Long.fromString(groupId);
        const buildRet = WeilaPbGroupWrapper.buildGetGroupOnlineMemberReq(groupIdLong);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the detail information of a group.
     * 
     * @param groupId the id of the group.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the detail information of the group.
     */
    public async weila_get_group_detail(groupId: string, timeout: number = 5000): Promise<WL.Group.RspGetGroupDetail> {
        const groupIdLong = Long.fromString(groupId);
        const buildRet = WeilaPbGroupWrapper.buildGetGroupDetailReq(groupIdLong);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the group invitations.
     * 
     * @param latestUpdated the latest update time of the group invitations.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the group invitations.
     */
    public async weila_get_group_invite_info(latestUpdated: number, timeout: number = 5000): Promise<WL.Group.RspGetGroupInviteInfo> {
        const buildRet = WeilaPbGroupWrapper.buildGetGroupInviteInfoReq(latestUpdated);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the group join information.
     * 
     * @param latestUpdated the latest update time of the group join information.
     * @param groupIds the ids of the groups to get the join information for.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the group join information.
     */
    public async weila_get_group_join_info(latestUpdated: number, groupIds: string[], timeout: number = 5000): Promise<WL.Group.RspGetGroupJoinInfo> {
        const groupIdsLong = groupIds.map(id => Long.fromString(id));
        const buildRet = WeilaPbGroupWrapper.buildGetGroupJoinInfoReq(latestUpdated, groupIdsLong);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the black list of a group.
     * 
     * @param groupId the id of the group.
     * @param pageIndex the index of the page to get.
     * @param pageSize the size of the page to get.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the black list of the group.
     */
    public async weila_get_group_black_list(groupId: string, pageIndex: number = 0, pageSize: number = 100, timeout: number = 5000): Promise<WL.Group.RspGetGroupBlackList> {
        const groupIdLong = Long.fromString(groupId);
        const buildRet = WeilaPbGroupWrapper.buildGetGroupBlackListReq(groupIdLong, pageIndex, pageSize);
        return this.sendServiceMessage(buildRet, timeout);
    }

    // friend service related functions
    
    /**
     * Get the friend information of the current user.
     * 
     * @param lastestUpdated the latest update time of the friend information.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the friend information of the current user.
     */
    public async weila_get_friend_info(lastestUpdated: number, timeout: number = 5000): Promise<WL.Friend.RspGetFriendInfo> {
        const buildRet = WeilaPbFriendWrapper.buildGetFriendInfoReq(lastestUpdated);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the user information of the given user ids.
     * 
     * @param userIds the ids of the users to get the information for.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the user information of the given user ids.
     */
    public async weila_get_user_info(userIds: number[], timeout: number = 5000): Promise<WL.Friend.RspGetFriendUserInfo> {
        const buildRet = WeilaPbFriendWrapper.buildGetFriendUserInfoReq(userIds);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the friend invitations of the current user.
     * 
     * @param latestUpdated the latest update time of the friend invitations.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the friend invitations of the current user.
     */
    public async weila_get_friend_invite_info(latestUpdated: number, timeout: number = 5000): Promise<WL.Friend.RspGetFriendInviteInfo> {
        const buildRet = WeilaPbFriendWrapper.buildGetFriendInviteInfoReq(latestUpdated);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the black list of friends.
     * 
     * @param pageIndex the index of the page to get.
     * @param pageSize the size of the page to get.
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the black list of friends.
     */
    public async weila_get_friend_black_list(pageIndex: number = 0, pageSize: number = 100, timeout: number = 5000): Promise<WL.Friend.RspGetFriendBlackList> {
        const buildRet = WeilaPbFriendWrapper.buildGetFriendBlackListReq(pageIndex, pageSize);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the online friends of the current user.
     * 
     * @param timeout the timeout for the request in milliseconds.
     * @returns a promise that resolves to the online friends of the current user.
     */
    public async weila_get_friend_online_friend(timeout: number = 5000): Promise<WL.Friend.RspGetOnlineFriend> {
        const buildRet = WeilaPbFriendWrapper.buildGetOnlineFriendReq();
        return this.sendServiceMessage(buildRet, timeout);
    }

    // session service related functions
    
    /**
     * Control the burst mode of a session.
     * 
     * @param session - The session information for which to control the burst mode.
     * @param burstType - The type of burst control to apply.
     * @param timeout - The timeout for the request in milliseconds.
     * @returns A promise that resolves to the response of the burst control request.
     */

    public async weila_burst_control(session: WL_SessionInfo, burstType: number, timeout: number = 5000): Promise<WL.Session.RspBurstControl> {
        const buildRet = WeilaPBSessionWrapper.buildBurstControlReq(session.sessionId!, session.sessionType!, burstType);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Get the session information from the server.
     * 
     * @param updateTime - The latest update time of the session information.
     * @param timeout - The timeout for the request in milliseconds.
     * @returns A promise that resolves to the session information.
     */
    public async weila_get_session_info(updateTime: number, timeout: number = 5000): Promise<WL.Session.RspGetSession> {
        const updateTimeLong = Long.fromNumber(updateTime);
        const buildRet = WeilaPBSessionWrapper.buildGetSessionReq(updateTimeLong);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Remove a session from the server.
     * 
     * @param session The session information to remove.
     * @param timeout The timeout for the request in milliseconds.
     * @returns A promise that resolves to the response of the remove session request.
     */
    public async weila_remove_session(session: WL_SessionInfo, timeout: number = 5000): Promise<WL.Session.RspRemoveSession> {
        const buildRet = WeilaPBSessionWrapper.buildRemoveSessionReq(session.sessionId!, session.sessionType!);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Read a message in a session.
     * 
     * @param session - The session information of the message to read.
     * @param msgId - The id of the message to read.
     * @param timeout - The timeout for the request in milliseconds.
     * @returns A promise that resolves to the response of the read message request.
     */
    public async weila_session_msg_read(session: WL_SessionInfo, msgId: number, timeout: number = 5000): Promise<WL.Session.RspMsgRead> {
        const buildRet = WeilaPBSessionWrapper.buildMsgReadReq(session.sessionId!, session.sessionType!, msgId);
        return this.sendServiceMessage(buildRet, timeout);    
    }

    /**
     * Retrieves unread messages in a session within a specified range.
     * 
     * @param session - The session information for which to get unread messages.
     * @param fromMsgId - The ID of the starting message in the range.
     * @param toMsgId - The ID of the ending message in the range.
     * @param timeout - The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing unread messages.
     */

    public async weila_get_unread_msg(session: WL_SessionInfo, fromMsgId: number, toMsgId: number, timeout: number = 5000): Promise<WL.Session.RspGetUnreadMsg> {
        const buildRet = WeilaPBSessionWrapper.buildMsgGetUnReadReq(session.sessionId!, session.sessionType!, fromMsgId, toMsgId);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Retrieves messages in a session within a specified range.
     * 
     * @param session - The session information for which to get messages.
     * @param msgId - The ID of the starting message in the range.
     * @param count - The number of messages to retrieve.
     * @param timeout - The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the retrieved messages.
     */
    public async weila_get_msgs(session: WL_SessionInfo, msgId: number, count: number, timeout: number = 5000): Promise<WL.Session.RspGetMsg> {
        const buildRet = WeilaPBSessionWrapper.buildMsgGetMsgReq(session.sessionId!, session.sessionType!, msgId, count);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Retrieves the online members in a session.
     * 
     * @param session The session information for which to get online members.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the online members.
     */
    public async weila_get_session_online_members(session: WL_SessionInfo, timeout: number = 5000): Promise<WL.Session.RspGetSessionOnlineMember> {
        const buildRet = WeilaPBSessionWrapper.buildGetSessionOnlineMembers(session.sessionId!, session.sessionType!);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Retrieves the user information in a session for a given list of user IDs.
     * 
     * @param session The session information for which to get user information.
     * @param userIds The IDs of the users for which to get information.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the user information.
     */
    public async weila_get_session_userinfos(session: WL_SessionInfo, userIds: number[], timeout: number = 5000): Promise<WL.Session.RspGetSessionUserInfo> {
        const buildRet = WeilaPBSessionWrapper.buildGetSessionUserInfos(session.sessionId!, session.sessionType!, userIds);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends a text message in a session.
     * 
     * @param session The session information for which to send the message.
     * @param text The text content of the message.
     * @param autoReply Whether or not the message should be auto-replied to.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_text_session_msg(session: WL_SessionInfo, text: string, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildTextSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, text);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends an image message in a session.
     * 
     * @param session The session information for which to send the message.
     * @param imageUrl The URL of the image to send.
     * @param autoReply Whether or not the message should be auto-replied to.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_image_session_msg(session: WL_SessionInfo, imageUrl: string, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildImageSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, imageUrl);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends a video message in a session.
     * 
     * @param session The session information for which to send the message.
     * @param mediaData The video data to send.
     * @param autoReply Whether or not the message should be auto-replied to.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_video_session_msg(session: WL_SessionInfo, mediaData: WL_MediaData, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildMediaInfoSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, mediaData, false);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends a file message in a session.
     * 
     * @param session The session information for which to send the message.
     * @param mediaData The file data to send.
     * @param autoReply Whether or not the message should be auto-replied to.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_file_session_msg(session: WL_SessionInfo, mediaData: WL_MediaData, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildMediaInfoSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, mediaData, true);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * @important
     * Sends a location message in a session.
     * 
     * @param session The session information for which to send the message.
     * @param shareLocationInfo The location data to send, using WL_LocationData
     * @param autoReply Whether or not the message should be auto-replied to.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_share_location_session_msg(session: WL_SessionInfo, shareLocationInfo: WL_LocationData, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildLocationInfoSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, shareLocationInfo);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends an audio message in a session.
     * 
     * @param session The session information for which to send the message.
     * @param audioData The audio data to send, either as a string URL or a binary Uint8Array.
     * @param autoReply Whether or not the message should be auto-replied to.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_audio_session_msg(session: WL_SessionInfo, audioData: string|Uint8Array, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildAudioSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, audioData);
        return this.sendServiceMessage(buildRet, timeout);
    }


    /**
     * Sends audio data using PTT (Push-To-Talk) wrapper.
     *
     * @param session - The session information.
     * @param audioData - The audio data to be sent as a Uint8Array.
     * @param autoReply - A boolean indicating whether to enable auto-reply.
     * @param timeout - The timeout duration in milliseconds (default is 5000).
     * @returns A promise that resolves to a WL.Session.RspMsg object.
     */
    public async weila_send_audio_by_with_ptt_wrapper(session: WL_SessionInfo, audioData: Uint8Array, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        let rspData;
        let pttPayloads = get_pttdata_array_by_audiodata(audioData);
        let seq = Math.floor(Date.now() / 1000);
        
        for (let i = 0; i < pttPayloads.length; i++) {
            let pttData = new WL.Session.PttData();
            if (i == 0) {
                pttData.marker = 0x00;
            }else if (i == pttPayloads.length - 1) {
                pttData.marker = 0x02;
            }else {
                pttData.marker = 0x01;
            }
            pttData.seq = seq;
            pttData.packageSeq = i;
            pttData.monitor = 0;
            pttData.source = 0;
            pttData.opusData = {
                frameCount: pttPayloads[i].frameCount,
                payload: pttPayloads[i].data
            };

            const buildRet = WeilaPBSessionWrapper.buildPttSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, pttData);
            rspData = await this.sendServiceMessage(buildRet, timeout);
        }
        
        return rspData;
    }

    /**
     * Sends a PTT (push-to-talk) message in a session.
     * 
     * @param session The session information for which to send the message.
     * @param pttData The PTT data to send, including the audio URL and the
     *              duration of the audio.
     * @param autoReply Whether or not the message should be auto-replied to.
     * @param timeout The maximum time to wait for the request in milliseconds.
     *              Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_ptt_session_msg(session: WL_SessionInfo, pttData: WL_PttData, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildPttSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, pttData);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Withdraws a message in a session.
     * 
     * @param session The session information of the message to withdraw.
     * @param msgId The id of the message to withdraw.
     * @param timeout The maximum time to wait for the request in milliseconds.
     *              Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_withdraw_session_msg(session: WL_SessionInfo, msgId: number, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildWithDrawMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, false, msgId);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends a switch session message in a session.
     * 
     * @param session - The session information for which to send the switch session message.
     * @param switchData - The data associated with the switch session message.
     * @param autoReply - Whether or not the message should be auto-replied to.
     * @param timeout - The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_switch_session_msg(session: WL_SessionInfo, switchData: string, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildSwitchSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, switchData);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends a service message in a session.
     * 
     * @param session - The session information for which to send the service message.
     * @param serviceData - The data associated with the service message.
     * @param autoReply - Whether or not the message should be auto-replied to.
     * @param timeout - The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the message ID.
     */
    public async weila_send_service_session_msg(session: WL_SessionInfo, serviceData: string, autoReply: boolean, timeout: number = 5000): Promise<WL.Session.RspMsg> {
        const loginUserInfo = WeilaStore.getInstance().getLoginUserInfo();
        const buildRet = WeilaPBSessionWrapper.buildServiceSessionMsgReq(session.sessionId!, session.sessionType!, loginUserInfo!.userId!, autoReply, serviceData);
        return this.sendServiceMessage(buildRet, timeout);
    }

    // business service related functions
    
    /**
     * Retrieves the session information for a given list of session IDs.
     * 
     * @param sessionIds The list of session IDs for which to retrieve the session information.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the session information.
     */
    public async weila_business_get_sessions(sessionIds: Long[], timeout: number = 5000): Promise<WL.Business.RspCommonGetSessions> {
        const buildRet = WeilaPbBusinessWrapper.buildCommonGetSessionsReq(sessionIds);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends a staff invite message in a service.
     * 
     * @param serviceId The service ID for which to send the staff invite message.
     * @param userIds The list of user IDs to invite.
     * @param content The content of the staff invite message. Default is an empty string.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the staff invite information.
     */
    public async weila_business_staff_invite(serviceId: number, userIds: number[], content: string = '', timeout: number = 5000): Promise<WL.Business.RspCommonGetStaffInvite> {
        const buildRet = WeilaPbBusinessWrapper.buildCommonStaffInviteReq(serviceId, userIds, content);
        return this.sendServiceMessage(buildRet, timeout); 
    }

    /**
     * Answers a staff invite in a service.
     * 
     * @param serviceId The service ID for which to answer the staff invite.
     * @param invitorId The user ID of the invitor.
     * @param status The answer status. 0: ACCEPT, 1: REJECT, 2: IGNORE.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the staff invite information.
     */
    public async weila_business_answer_staff_invite(serviceId: number, invitorId: number, status: number, timeout: number = 5000): Promise<WL.Business.RspCommonAnswerStaffInvite> {
        const buildRet = WeilaPbBusinessWrapper.buildCommonAnswerStaffInviteReq(serviceId, invitorId, status);
        return this.sendServiceMessage(buildRet, timeout); 
    }

    /**
     * Retrieves the staff invite information for a business service.
     * 
     * @param latestUpdated The latest update time of the staff invites.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the staff invite information.
     */

    public async weila_business_get_staff_invite(latestUpdated: number, timeout: number = 5000): Promise<WL.Business.RspCommonGetStaffInvite> {
        const buildRet = WeilaPbBusinessWrapper.buildCommonGetStaffInviteReq(latestUpdated);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Exit a staff in a service.
     * 
     * @param serviceId The service ID for which to exit the staff.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the staff exit information.
     */
    public async weila_business_staff_exit(serviceId: number, timeout: number = 5000): Promise<WL.Business.RspCommonStaffExit> {
        const buildRet = WeilaPbBusinessWrapper.buildCommonStaffExitReq(serviceId);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Deletes a staff in a service.
     * 
     * @param serviceId The service ID for which to delete the staff.
     * @param userId The user ID of the staff to delete.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the staff delete information.
     */
    public async weila_business_staff_delete(serviceId: number, userId: number, timeout: number = 5000): Promise<WL.Business.RspCommonStaffDelete> {
        const buildRet = WeilaPbBusinessWrapper.buildCommonStaffDeleteReq(serviceId, userId);
        return this.sendServiceMessage(buildRet, timeout);        
    }

    /**
     * Closes a customer session.
     * 
     * @param sessionId The ID of the customer session to close.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the result of the close session request.
     */
    public async weila_business_close_session(sessionId: Long, timeout: number = 5000): Promise<WL.Business.RspCustomerCloseSession> {
        const buildRet = WeilaPbBusinessWrapper.buildCustomerCloseSessionReq(sessionId);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Sends a customer active session message.
     * 
     * @param sessionId The ID of the customer session to send the message to.
     * @param content The content of the message to send.
     * @param timeout The maximum time to wait for the request in milliseconds. Default is 5000 ms.
     * @returns A promise that resolves to the response containing the result of the active session request.
     */
    public async weila_business_active_session(sessionId: Long, content: string, timeout: number = 5000): Promise<WL.Business.RspCustomerActiveSession> {
        const buildRet = WeilaPbBusinessWrapper.buildCustomerActiveSessionReq(sessionId, content);
        return this.sendServiceMessage(buildRet, timeout);
    }

    /**
     * Retrieves the session information from raw session data.
     * 
     * @param sessionRaws The raw session data to convert.
     * @returns An array of WL_SessionInfo objects containing the session information.
     */
    public weila_get_session_info_from_raw_session_data(sessionRaws: WL.Session.ISessionInfo[]): WL_SessionInfo[] {
        const userInfo = WeilaStore.getInstance().getLoginUserInfo();
        if (userInfo == null) {
            return [];
        }

        return convertFromSessionRaw(sessionRaws, userInfo.userId!);
    }

    /**
     * Retrieves the message data from raw message data.
     * 
     * @param msgRaws The raw message data to convert.
     * @returns An array of WL_MsgData objects containing the message data.
     */
    public weila_get_msg_data_from_raw_msg_data(msgRaws: WL.Session.IMsgData[]): WL_MsgData[] {
        const userInfo = WeilaStore.getInstance().getLoginUserInfo();
        if (userInfo == null) {
            console.error("weila_get_msg_data_from_raw_msg_data: userInfo is null");
            return [];
        }

        return convertFromMsgDataRaws(msgRaws, userInfo.userId!);
    }

    /**
     * Retrieves the storage instance of Weila.
     * 
     * @returns The WeilaStore instance.
     */
    public weila_get_storage_instance(): WeilaStore {
        return WeilaStore.getInstance();
    }
}